/*
 * Copyright (C) 2008 Luca Veltri - University of Parma - Italy
 * 
 * This source code is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This source code is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this source code; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 * Author(s):
 * Luca Veltri (luca.veltri@unipr.it)
 */

package org.mjsip.net;


import java.io.InterruptedIOException;

import org.zoolu.net.IpAddress;
import org.zoolu.net.UdpPacket;
import org.zoolu.net.UdpSocket;


/** UdpRelay implements a direct UDP datagram relay agent. 
  * It receives UDP packets at a local port and relays them toward a remote UDP socket
  * (destination address/port).
  */
public class UdpRelay extends Thread {
	
	// The maximum IP packet size
	//public static final int MAX_PKT_SIZE=2000;
	public static final int MAX_PKT_SIZE=32000;

	/** Local receiver/sender port */
	int local_port;  
	/** Remote source address */
	IpAddress src_addr;
	/** Remote source port */
	int src_port;  
	/** Destination address */
	IpAddress dest_addr;
	/** Destination port */
	int dest_port;  
	/** Whether it is running */
	boolean stop;
	/** Maximum time that the UDP relay can remain active after been halted (in milliseconds) */
	int socket_to=3000; // 3sec 
	/** Maximum time that the UDP relay remains active without receiving UDP datagrams (in seconds) */
	int alive_to=60; // 1min 
	/** UdpRelay listener */
	UdpRelayListener listener;   
	  
	/** Creates a new UDP relay and starts it.
	  * <p> The UdpRelay remains active until method halt() is called. */
	public UdpRelay(int local_port, String dest_addr, int dest_port, UdpRelayListener listener) {
		init(local_port,dest_addr,dest_port,0,listener);
		start();
	}

	/** Creates a new UDP relay and starts it.
	  * <p> The UdpRelay will automatically stop after <i>alive_time</i> seconds
	  *     of idle time (i.e. without receiving UDP datagrams) */
	public UdpRelay(int local_port, String dest_addr, int dest_port, int alive_time, UdpRelayListener listener) {
		init(local_port,dest_addr,dest_port,alive_time,listener);
		start();
	}
	 
	/** Inits a new UDP relay */
	private void init(int local_port, String dest_addr, int dest_port, int alive_time, UdpRelayListener listener) {
		this.local_port=local_port;     
		this.dest_addr=new IpAddress(dest_addr);
		this.dest_port=dest_port;
		this.alive_to=alive_time;
		this.listener=listener;
		src_addr=new IpAddress("0.0.0.0");
		src_port=0;
		stop=false;
	}

	/** Gets the local receiver/sender port */
	public int getLocalPort() {
		return local_port;
	}

	/** Gets the destination address */
	/*public String getDestAddress() {
		return dest_addr;
	}*/

	/** Gets the destination port */
	/*public int getDestPort() {
		return dest_port;
	}*/

	/** Sets a new destination address */
	public UdpRelay setDestAddress(String dest_addr) {
		this.dest_addr=new IpAddress(dest_addr);
		return this;
	}

	/** Sets a new destination port */
	public UdpRelay setDestPort(int dest_port) {
		this.dest_port=dest_port;
		return this;
	}

	/** Whether the UDP relay is running */
	public boolean isRunning() {
		return !stop;
	}

	/** Stops the UDP relay */
	public void halt() {
		stop=true;
	}

	/** Sets the maximum time that the UDP relay can remain active after been halted */
	public void setSoTimeout(int so_to) {
		socket_to=so_to;
	}

	/** Gets the maximum time that the UDP relay can remain active after been halted */
	public int getSoTimeout() {
		return socket_to;
	}
		 
	/** Redirect packets received from remote source addr/port to destination addr/port  */
	@Override
	public void run() {
		//System.out.println("DEBUG: starting UdpRelay "+toString()+" (it expires after "+alive_to+" sec)");     
		try    {
			UdpSocket socket=new UdpSocket(local_port);
			byte []buf=new byte[MAX_PKT_SIZE];
									
			socket.setSoTimeout(socket_to);
			// datagram packet
			UdpPacket packet=new UdpPacket(buf, buf.length);
			
			// convert alive_to in milliseconds
			long keepalive_to=((1000)*(long)alive_to)-socket_to;
			
			// end time
			long expire=System.currentTimeMillis()+keepalive_to;
			// whether reset the receiver
			//boolean reset=true;
			
			while(!stop) {
				// non-blocking receiver
				try {
					socket.receive(packet);           
				}
				catch (InterruptedIOException ie) {
					// if expired, stop relaying
					if (alive_to>0 && System.currentTimeMillis()>expire) halt();
					continue;
				}
				// check whether the source address and port are changed
				if (src_port!=packet.getPort() || !src_addr.equals(packet.getIpAddress())) {
					src_port=packet.getPort();
					src_addr=packet.getIpAddress();
					if (listener!=null) listener.onUdpRelaySourceChanged(this,src_addr.toString(),src_port);
				}
				// relay
				packet.setIpAddress(dest_addr);
				packet.setPort(dest_port);
				socket.send(packet);
				// reset
				packet=new UdpPacket(buf, buf.length);
				expire=System.currentTimeMillis()+keepalive_to;
			}
			socket.close();
			if (listener!=null) listener.onUdpRelayTerminated(this);
		}
		catch (Exception e) { e.printStackTrace(); } 
	}
	
	
		
	/** Gets a String representation of the Object */
	@Override
	public String toString() {
		return "localhost:"+Integer.toString(local_port)+"-->"+dest_addr+":"+dest_port;
	}


	// ********************************** MAIN *********************************

	/** The main method. */
	public static void main(String[] args) {
		
		if (args.length<3) {
			
			System.out.println("usage:\n   java UdpRelay <local_port> <address> <port> [ <alive_time> ]");
			System.exit(0);
		}
		
		int local_port=Integer.parseInt(args[0]);
		int remote_port=Integer.parseInt(args[2]);
		String remote_address=args[1];
		
		int alive_time=0;
		if (args.length>3) alive_time=Integer.parseInt(args[3]);
		
		new UdpRelay(local_port,remote_address,remote_port,alive_time,null);
	}
}
 
